package com.dhz.common.utils.security;

/**
 * DES扩展工具类,保持JS与JAVA加密结果一致.
 * 代码中有一些int数组的设定是根据DES算法设定.
 *
 * @author fyduan
 */
public final class DESExtUtils {
    private DESExtUtils() {
    }

    /**
     * 解密.
     *
     * @param origStr       待加密的字符串
     * @param firstKey      加密因子-1
     * @param secondKey     加密因子-2
     * @param thirdKey      加密因子-3
     * @return 返回原始字符串
     */
    public static String strEnc(String origStr, String firstKey, String secondKey, String thirdKey) {
        // 空字符串加密结果为""
        if (isBlank(origStr)) return "";

        StringBuilder encData = new StringBuilder();
        int len = origStr.length();
        int iterator = len / 4 + (len % 4 == 0 ? 0 : 1);

        // 加密盐准备
        int[][] firstKeyBt = null, secondKeyBt = null, thirdKeyBt = null;
        if (isNotBlank(firstKey)) firstKeyBt = getKeyBytes(firstKey);
        if (isNotBlank(secondKey)) secondKeyBt = getKeyBytes(secondKey);
        if (isNotBlank(thirdKey)) thirdKeyBt = getKeyBytes(thirdKey);


        // 4倍数部分
        for (int i = 0; i < iterator; i++) {
            int start = i * 4, end = start + 4;
            // 处理待补齐部分
            String tempData = origStr.substring(start, end > len ? len : end);
            int[] encByte = strToBt(tempData);

            encByte = enc(encByte, firstKeyBt);
            encByte = enc(encByte, secondKeyBt);
            encByte = enc(encByte, thirdKeyBt);

            encData.append(bt64ToHex(encByte));
        }

        return encData.toString();
    }

    /**
     * 解密.
     *
     * @param data          加密后的字符串
     * @param firstKey      加密因子-1
     * @param secondKey     加密因子-2
     * @param thirdKey      加密因子-3
     * @return 返回原始字符串
     */
    public static String strDec(String data, String firstKey, String secondKey, String thirdKey) {
        int len = data.length();
        StringBuilder decStr = new StringBuilder();

        // 加密盐准备
        int[][] firstKeyBt = null, secondKeyBt = null, thirdKeyBt = null;
        if (isNotBlank(firstKey)) firstKeyBt = getKeyBytes(firstKey);
        if (isNotBlank(secondKey)) secondKeyBt = getKeyBytes(secondKey);
        if (isNotBlank(thirdKey)) thirdKeyBt = getKeyBytes(thirdKey);

        int iterator = len / 16;
        for (int i = 0; i < iterator; i++) {
            String tempData = data.substring(i * 16, (i+1) * 16);
            String strByte = hexToBt64(tempData);
            int[] intByte = new int[64];
            for (int j = 0; j < 64; j++) {
                intByte[j] = Integer.parseInt(strByte.substring(j, j + 1));
            }

            intByte = dec(intByte, thirdKeyBt);
            intByte = dec(intByte, secondKeyBt);
            intByte = dec(intByte, firstKeyBt);

            decStr.append(byteToString(intByte));
        }
        return decStr.toString();
    }

    // 加密统一模式
    private static int[] enc(int[] origBytes, int[][] keyBytes) {
        if (keyBytes != null && origBytes != null) {
            for (int[] keyByte : keyBytes) {
                origBytes = _enc(origBytes, keyByte);
            }
        }
        return origBytes;
    }

    // 解密统一模式
    private static int[] dec(int[] origBytes, int[][] keyBytes) {
        if (keyBytes != null && origBytes != null) {
            for (int i = keyBytes.length - 1; i >= 0; i--) {
                origBytes = _dec(origBytes, keyBytes[i]);
            }
        }
        return origBytes;
    }

    /**
     * 加密.
     *
     * @param dataByte 待加密数据
     * @param keyByte  秘钥数据
     * @return int[]
     */
    private static int[] _enc(int[] dataByte, int[] keyByte) {
        // 根据传入的key,生成一个16*48的秘钥组
        int[][] keys = generateKeys(keyByte);
        // 初始置换结果分块
        int[][] ipByteBlock = lrBlock(initPermute(dataByte));
        int[] ipLeft = ipByteBlock[0];
        int[] ipRight = ipByteBlock[1];

        // 正序加密: 16次
        int[] tempLeft = new int[32];
        for (int i = 0; i < 16; i++) {
            for (int j = 0; j < 32; j++) {
                tempLeft[j] = ipLeft[j];
                ipLeft[j] = ipRight[j];
            }

            int[] tempRight = xor(pPermute(sBoxPermute(xor(expandPermute(ipRight), keys[i]))), tempLeft);
            System.arraycopy(tempRight, 0, ipRight, 0, 32);
        }

        return finallyPermute(merge(ipRight, ipLeft));
    }

    /**
     * 解密.
     *
     * @param dataByte 待解密数据
     * @param keyByte  密钥数据
     * @return int[]
     */
    private static int[] _dec(int[] dataByte, int[] keyByte) {
        // 根据传入的key,生成一个16*48的秘钥组
        int[][] keys = generateKeys(keyByte);
        // 初始置换结果分块
        int[][] ipByteBlock = lrBlock(initPermute(dataByte));
        int[] ipLeft = ipByteBlock[0];
        int[] ipRight = ipByteBlock[1];

        // 反序解密: 16次
        int[] tempLeft = new int[32];
        for (int i = 15; i >= 0; i--) {
            for (int j = 0; j < 32; j++) {
                tempLeft[j] = ipLeft[j];
                ipLeft[j] = ipRight[j];
            }

            int[] tempRight = xor(pPermute(sBoxPermute(xor(expandPermute(ipRight), keys[i]))), tempLeft);
            System.arraycopy(tempRight, 0, ipRight, 0, 32);
        }
        return finallyPermute(merge(ipRight, ipLeft));
    }

    // 数据分块
    private static int[][] lrBlock(int[] data) {
        int len = data.length;
        if (len % 2 != 0) {
            len = len + 1;
        }
        len = len / 2;

        int[] left = new int[len];
        int[] right = new int[len];

        for (int i = 0; i < len; i++) {
            left[i] = data[i];
            right[i] = data[len + i];
        }

        return new int[][]{left, right};
    }

    // 合并数据
    private static int[] merge(int[] pre, int[] next) {
        int maxLen = Math.max(pre.length, next.length);
        int[] finalData = new int[maxLen * 2];
        for (int i = 0; i < maxLen; i++) {
            finalData[i] = pre[i];
            finalData[maxLen + i] = next[i];
        }
        return finalData;
    }

    /**
     * 将数据进行初始置换,得到64位的初始密文.
     * <pre>
     *     1. 置换表为:
     *      |- left : {
     *          56, 48, 40, 32, 24, 16, 8,
     *          1, 57, 49, 41, 33, 25, 17,
     *          9, 1, 58, 50, 42, 34, 26,
     *          18, 10, 2, 59, 51, 43, 35
     *      }
     *      |- right: {
     *          62, 54, 46, 38, 30, 22, 14,
     *          6, 61, 53, 45, 37, 29, 21,
     *          13, 5, 60, 52, 44, 36, 28,
     *          20, 12, 4, 27, 19, 11, 3
     *      }
     *     2. 放弃其中的8，16，24，32，40，48，56，64位置,这些位置作为奇偶校验位;有效数据56位
     * </pre>
     *
     * @param originalData 初始值,64位
     * @return int[]
     */
    public static int[] initPermute(int[] originalData) {
        int[] ipByte = new int[64];
        for (int i = 0, m = 1, n = 0; i < 4; i++, m += 2, n += 2) {
            for (int j = 7, k = 0; j >= 0; j--, k++) {
                ipByte[i * 8 + k] = originalData[j * 8 + m];
                ipByte[i * 8 + k + 32] = originalData[j * 8 + n];
            }
        }
        return ipByte;
    }

    public static int[] expandPermute(int[] rightData) {
        int[] epByte = new int[48];
        for (int i = 0; i < 8; i++) {
            if (i == 0) {
                epByte[i * 6] = rightData[31];
            } else {
                epByte[i * 6] = rightData[i * 4 - 1];
            }
            epByte[i * 6 + 1] = rightData[i * 4];
            epByte[i * 6 + 2] = rightData[i * 4 + 1];
            epByte[i * 6 + 3] = rightData[i * 4 + 2];
            epByte[i * 6 + 4] = rightData[i * 4 + 3];
            if (i == 7) {
                epByte[i * 6 + 5] = rightData[0];
            } else {
                epByte[i * 6 + 5] = rightData[i * 4 + 4];
            }
        }
        return epByte;
    }

    public static int[] xor(int[] byteOne, int[] byteTwo) {
        int[] xorByte = new int[byteOne.length];
        for (int i = 0; i < byteOne.length; i++) {
            xorByte[i] = byteOne[i] ^ byteTwo[i];
        }
        return xorByte;
    }

    public static int[] sBoxPermute(int[] expandByte) {

        int[] sBoxByte = new int[32];
        String binary = "";
        int[][] s1 = {
                { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7 },
                { 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8 },
                { 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0 },
                { 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 } };

        /* Table - s2 */
        int[][] s2 = {
                { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10 },
                { 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5 },
                { 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15 },
                { 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 } };

        /* Table - s3 */
        int[][] s3 = {
                { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8 },
                { 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1 },
                { 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7 },
                { 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 } };
        /* Table - s4 */
        int[][] s4 = {
                { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15 },
                { 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9 },
                { 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4 },
                { 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 } };

        /* Table - s5 */
        int[][] s5 = {
                { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9 },
                { 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6 },
                { 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14 },
                { 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 } };

        /* Table - s6 */
        int[][] s6 = {
                { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11 },
                { 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8 },
                { 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6 },
                { 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 } };

        /* Table - s7 */
        int[][] s7 = {
                { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1 },
                { 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6 },
                { 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2 },
                { 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 } };

        /* Table - s8 */
        int[][] s8 = {
                { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7 },
                { 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2 },
                { 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8 },
                { 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } };

        for (int m = 0; m < 8; m++) {
            int i = expandByte[m * 6] * 2 + expandByte[m * 6 + 5];
            int j = expandByte[m * 6 + 1] * 2 * 2 * 2 + expandByte[m * 6 + 2] * 2 * 2
                    + expandByte[m * 6 + 3] * 2 + expandByte[m * 6 + 4];
            switch (m) {
                case 0:
                    binary = getBoxBinary(s1[i][j]);
                    break;
                case 1:
                    binary = getBoxBinary(s2[i][j]);
                    break;
                case 2:
                    binary = getBoxBinary(s3[i][j]);
                    break;
                case 3:
                    binary = getBoxBinary(s4[i][j]);
                    break;
                case 4:
                    binary = getBoxBinary(s5[i][j]);
                    break;
                case 5:
                    binary = getBoxBinary(s6[i][j]);
                    break;
                case 6:
                    binary = getBoxBinary(s7[i][j]);
                    break;
                case 7:
                    binary = getBoxBinary(s8[i][j]);
                    break;
            }
            sBoxByte[m * 4] = Integer.parseInt(binary.substring(0, 1));
            sBoxByte[m * 4 + 1] = Integer.parseInt(binary.substring(1, 2));
            sBoxByte[m * 4 + 2] = Integer.parseInt(binary.substring(2, 3));
            sBoxByte[m * 4 + 3] = Integer.parseInt(binary.substring(3, 4));
        }
        return sBoxByte;
    }

    public static int[] pPermute(int[] sBoxByte) {
        int[] pBoxPermute = new int[32];
        int[] per = {
                15, 6, 19, 20, 28, 11, 27, 16,
                0, 14, 22, 25, 4, 17, 30, 9,
                1, 7, 23, 13, 31, 26, 2, 8,
                18, 12, 29, 5, 21, 10, 3, 24
        };

        for (int i = 0; i < 32; i++) {
            pBoxPermute[i] = sBoxByte[per[i]];
        }

        return pBoxPermute;
    }

    // 最终置换
    public static int[] finallyPermute(int[] endByte) {
        int[] fpByte = new int[64];
        int[] per = {
                39, 7, 47, 15, 55, 23, 63, 31,
                38, 6, 46, 14, 54, 22, 62, 30,
                37, 5, 45, 13, 53, 21, 61, 29,
                36, 4, 44, 12, 52, 20, 60, 28,
                35, 3, 43, 11, 51, 19, 59, 27,
                34, 2, 42, 10, 50, 18, 58, 26,
                33, 1, 41, 9, 49, 17, 57, 25,
                32, 0, 40, 8, 48, 16, 56, 24
        };

        for (int i = 0; i < 64; i++) {
            fpByte[i] = endByte[per[i]];
        }

        return fpByte;
    }

    /**
     * 用56位密钥对64位的明文数据块进行加密，每次加密可以对64位的明文数据进行16轮编码，
     * 经过一系列替换和移位后，输出完全不同的64位密文数据.
     *
     * @param keyByte 64位的数据.
     * @return 密钥组
     */
    public static int[][] generateKeys(int[] keyByte) {
        // 迭代加密次数
        int loopEncNum = 16;
        // 循环左移位数
        int[] loopLeftShiftNum = new int[] {1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1};
        // 置换选择位-2: 从循环移位得到的56位中选出48位
        int[] cr2 = {
                13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9,
                22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
                40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
                43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31
        };

        // 其余8位为奇偶校验位
        int[] key = new int[56];
        int[][] keys = new int[16][48];

        // 提取循环加密的密钥
        for (int i = 0; i < 7; i++) {
            for (int j = 0, k = 7; j < 8; j++, k--) {
                key[i * 8 + j] = keyByte[8 * k + i];
            }
        }

        // 循环加密: 将输入的64位数据块经过初始置换按位重新组合，并把输入分为左右两部分（L、R），各部分各有32位
        for (int i = 0; i < loopEncNum; i++) {
            // 数组移位
            {
                int tempLeft;
                int tempRight;
                for (int j = 0; j < loopLeftShiftNum[i]; j++) {
                    tempLeft = key[0];
                    tempRight = key[28];
                    for (int k = 0; k < 27; k++) {
                        key[k] = key[k + 1];
                        key[28 + k] = key[29 + k];
                    }
                    key[27] = tempLeft;
                    key[55] = tempRight;
                }
            }

            int[] tempKey = new int[48];
            for (int i1 = 0; i1 < tempKey.length; i1++) {
                tempKey[i1] = key[cr2[i1]];
            }

            // 将生成的秘钥拷贝到秘钥组中
            System.arraycopy(tempKey, 0, keys[i], 0, 48);
        }

        return keys;
    }

    // -----------------------------------------------------------------------------------------------
    // -----------------------------------------------------------------------------------------------

    public static final String[] HEX_BINS = {"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"};
    public static final String[] HEX_STR = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"};

    // 将HEX数据转换为2进制的字符串
    public static String hexToBt64(String hex) {
        StringBuilder binary = new StringBuilder(64);
        for (int i = 0; i < 16; i++) {
            binary.append(hexToBt4(hex.substring(i, i + 1)));
        }
        return binary.toString();
    }

    // 转换10进制数字为二进制字符串
    public static String getBoxBinary(int i) {
        return HEX_BINS[i];
    }

    /*
     * 将16进制字符转换为其对应的2进制字符串
     *
     * @return 数字0~15对应二进制数据字符串长度为4,如:10001,0001
     */
    public static String hexToBt4(String hex) {
        return HEX_BINS[Integer.valueOf(hex, 16)];
    }

    /*
    * 将2进制数字(长度为4)转换为其对应的16进制字符.
    *
    * return hex
    */
    public static String bt4ToHex(String binary) {
        return HEX_STR[Integer.valueOf(binary, 2)];
    }

    /*
     * 转换64位bit成字符串
     *
     * return string
     */
    public static String byteToString(int[] byteData) {
        StringBuilder str = new StringBuilder(4);
        for (int i = 0; i < 4; i++) {
            int count = 0;
            for (int j = 0; j < 16; j++) {
                int pow = 1;
                for (int m = 15; m > j; m--) {
                    pow *= 2;
                }
                count += byteData[16 * i + j] * pow;
            }
            if (count != 0) {
                str.append((char)count);
            }
        }
        return str.toString();
    }

    /**
     * 转换字符串的bit为Hex String
     * @param byteData int[]
     * @return string
     */
    public static String bt64ToHex(int[] byteData) {
        StringBuilder hex = new StringBuilder();
        for (int i = 0; i < 16; i++) {
            String bt = "";
            for (int j = 0; j < 4; j++) {
                bt += byteData[i * 4 + j];
            }
            hex.append(bt4ToHex(bt));
        }
        return hex.toString();
    }

    /*
     * 将长度小于等于4的字符串转换为64为的bit数组
     *
     * return bit array(it's length = 64)
     */
    public static int[] strToBt(String str) {
        int len = str.length();
        int[] bt = new int[64];
        // 空位补齐值
        int align = 0;

        for (int i = 0; i < 4; i++) {
            int k = i < len ? str.charAt(i) : align;
            for (int j = 0; j < 16; j++) {
                int pow = 1;
                for (int m = 15; m > j; m--) {
                    pow *= 2;
                }
                bt[16 * i + j] = (k / pow) % 2;
            }
        }
        return bt;
    }

    /*
     * 转换字符串为长度64的二进制数组.
     *
     * @return 二进制数组
     */
    public static int[][] getKeyBytes(String key) {
        int len = key.length();
        int iterator = (len / 4);
        int remainder = len % 4;

        int[][] keyBytes = new int[iterator + (remainder==0?0:1)][64];

        int i;
        for (i = 0; i < iterator; i++) {
            keyBytes[i] = strToBt(key.substring(i * 4, (i+1) * 4));
        }
        if (remainder > 0) {
            keyBytes[i] = strToBt(key.substring(i * 4, len));
        }
        return keyBytes;
    }

    // --------------------- 为避免引入依赖,这里实现common-lang里面的部分方法 -------------------------
    // 空字符串判断
    public static boolean isBlank(CharSequence cs) {
        int strLen;
        if(cs != null && (strLen = cs.length()) != 0) {
            for(int i = 0; i < strLen; ++i) {
                if(!Character.isWhitespace(cs.charAt(i))) {
                    return false;
                }
            }

            return true;
        } else {
            return true;
        }
    }
    public static boolean isNotBlank(CharSequence cs) {
        return !isBlank(cs);
    }

}